#include <stdio.h>
#include <windows.h>
#include <setupapi.h>
#include <devguid.h>
#include <regstr.h>
#include <string.h>
#include <iostream>
#include "UsbCdcDevice.h"

//Use the formatting: "Vid_xxxx&Pid_xxxx" where xxxx is a 16-bit hexadecimal number.
#define MY_DEVICE_ID  "Vid_04d8&Pid_000a"

//singlton
static UsbCdcDeviceC* usbCdcDevice = NULL;

UsbCdcDeviceC* UsbCdcDeviceC::instance()
{
	if (usbCdcDevice == NULL)
	{
		usbCdcDevice = new UsbCdcDeviceC();
	}
	return usbCdcDevice;
}

void UsbCdcDeviceC::init()
{
	m_threadId = 1;
	if ((m_thread = CreateThread(NULL, 0, usbCdcDeviceThreadTask, 0, 0, &m_threadId)) == NULL)
	{
		ExitProcess(0);
	}
}

UsbCdcDeviceC::UsbCdcDeviceC()
{
	m_timer = SetTimer(0, 0, 5000, (TIMERPROC)onTimer);
	m_comPort = "";
	m_comPortHandle = NULL;
}

void UsbCdcDeviceC::close()
{
	KillTimer(0,0);

	HANDLE Array_Of_Thread_Handles[1];
	Array_Of_Thread_Handles[0] = m_thread;
	PostThreadMessage(m_threadId, USB_DCD_MSG_EXIT, 0, 0);
	WaitForMultipleObjects(1, Array_Of_Thread_Handles, TRUE, INFINITE);
	CloseHandle(m_thread);

	if (m_comPortHandle != NULL)
	{
		CloseHandle(m_comPortHandle);
		m_comPortHandle = NULL;
	}
}

UsbCdcDeviceC::~UsbCdcDeviceC()
{
}

void UsbCdcDeviceC::comPortSet(string comPort)
{
	m_comPort = comPort;
	if (m_comPort == "")
	{
		if (m_comPortHandle != NULL)
		{
			CloseHandle(m_comPortHandle);
			m_comPortHandle = NULL;
		}
	}
	else
	{
		if (m_comPortHandle == NULL)
		{
			m_comPortHandle = CreateFile(m_comPort.c_str(), 
				GENERIC_READ | GENERIC_WRITE, 0, 0,
				OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0);
		}
	}
}

bool UsbCdcDeviceC::isDeviceAttachedGet()
{
	return m_comPort != "" ? true : false;
}

bool UsbCdcDeviceC::postMessage(UINT message)
{
	bool ret = false;

	if (m_thread)
	{
		ret = PostThreadMessage(m_threadId, message, 0, 0);
	}

	return ret;
}

DWORD WINAPI UsbCdcDeviceC::usbCdcDeviceThreadTask(LPVOID lpParam)
{
    for (;;)
    {
        MSG msg;
        BOOL MsgReturn = GetMessage(&msg, NULL, USB_DCD_MSG_INIT, USB_DCD_MSG_EXIT);
        if ( MsgReturn )
        {
            switch(msg.message)
            {
            case USB_DCD_MSG_INIT:
                cout << "USB_DCD_MSG_INIT Message Received.... " << endl;
                break;
            case USB_DCD_MSG_UPDATE:
                cout << "USB_DCD_MSG_UPDATE Message Received.... " << endl;
                break;
            case USB_DCD_MSG_CENTER:
                cout << "USB_DCD_MSG_CENTER Message Received.... " << endl;
				UsbCdcDeviceC::instance()->serialWrite("SERV00-C", 8); 
                break;
            case USB_DCD_MSG_LEFT:
                cout << "USB_DCD_MSG_LEFT Message Received.... " << endl;
				UsbCdcDeviceC::instance()->serialWrite("SERV00-L", 8); 
                break;
            case USB_DCD_MSG_RIGHT:
                cout << "USB_DCD_MSG_RIGHT Message Received.... " << endl;
				UsbCdcDeviceC::instance()->serialWrite("SERV00-R", 8); 
                break;

            case USB_DCD_MSG_EXIT:
                cout << "Exiting Message... " << endl;
                return 0;
            }
        }
    }
    return 0;
}

void UsbCdcDeviceC::onTimer(HWND hwnd, UINT uMsg, UINT idEvent,DWORD dwTime)
{
	UsbCdcDeviceC::instance()->comPortSet(UsbCdcDeviceC::instance()->checkIfPresentAndGetCOMString(MY_DEVICE_ID));
}

string UsbCdcDeviceC::toLower(string str) 
{
    for (int i=0; i<strlen(str.c_str()); i++)
	{
		if (str[i] >= 0x41 && str[i] <= 0x5A)
		{
			str[i] = str[i] + 0x20;
		}
	}
    return str;
}

BOOL UsbCdcDeviceC::serialWrite(char* lpBuf, DWORD dwToWrite)
{
	OVERLAPPED osWrite = {0};
	DWORD dwWritten;
	DWORD dwRes;
	BOOL fRes;
    
	// Create this write operation's OVERLAPPED structure's hEvent.
	osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	if (osWrite.hEvent == NULL)
	{
		// error creating overlapped event handle
		return FALSE;
	}
     
	// Issue write.
	if (!WriteFile(m_comPortHandle, lpBuf, dwToWrite, &dwWritten, &osWrite)) 
	{
		if (GetLastError() != ERROR_IO_PENDING)
		{
			// WriteFile failed, but isn't delayed. Report error and abort.
			fRes = FALSE;
		}
		else
		{
			// Write is pending.
			dwRes = WaitForSingleObject(osWrite.hEvent, INFINITE);
		}
		switch(dwRes)
		{
			// OVERLAPPED structure's event has been signaled.
			case WAIT_OBJECT_0:
				if (!GetOverlappedResult(m_comPortHandle, &osWrite, &dwWritten, FALSE))
				{
					fRes = FALSE;
				}
				else
				{
					// Write operation completed successfully.
					fRes = TRUE;
				}
			break;
     
			default:
				// An error has occurred in WaitForSingleObject.
				// This usually indicates a problem with the
				// OVERLAPPED structure's event handle.
				fRes = FALSE;
			break;
		}
	}
	else
	{
		// WriteFile completed immediately.
		fRes = TRUE;
	}
    
	CloseHandle(osWrite.hEvent);
	return fRes;
}

string UsbCdcDeviceC::checkIfPresentAndGetCOMString(string DeviceIDString)
{
	//Globally Unique Identifier (GUID) for COM port devices.  Windows uses GUIDs to identify things.
	GUID InterfaceClassGuid = {0xa5dcbf10, 0x6530, 0x11d2, 0x90, 0x1f, 0x00, 0xc0, 0x4f, 0xb9, 0x51, 0xed};

	HDEVINFO DeviceInfoTable = INVALID_HANDLE_VALUE;
	PSP_DEVICE_INTERFACE_DATA InterfaceDataStructure = new SP_DEVICE_INTERFACE_DATA;
	PSP_DEVICE_INTERFACE_DETAIL_DATA DetailedInterfaceDataStructure = new SP_DEVICE_INTERFACE_DETAIL_DATA;
	SP_DEVINFO_DATA DevInfoData;

	DWORD InterfaceIndex = 0;
	DWORD StatusLastError = 0;
	DWORD dwRegType;
	DWORD dwRegSize;
	DWORD StructureSize = 0;
	PBYTE PropertyValueBuffer;
	DWORD ErrorStatus;
	BOOL BoolStatus = FALSE;
	DWORD LoopCounter = 0;

	string DeviceIDToFind = DeviceIDString;
	string FriendlyNameString = "";
	int IndexOfStartofCOMChars;
	int IndexOfEndOfCOMChar = 0;
	string COMString = "";

	//First populate a list of plugged in devices (by specifying "DIGCF_PRESENT"), which are of the specified class GUID. 
	DeviceInfoTable = SetupDiGetClassDevs(&InterfaceClassGuid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);

	//Now look through the list we just populated.  We are trying to see if any of them match our device. 
	while(true)
	{
		InterfaceDataStructure->cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
		if (SetupDiEnumDeviceInterfaces(DeviceInfoTable, NULL, &InterfaceClassGuid, InterfaceIndex, InterfaceDataStructure))
		{
			ErrorStatus = GetLastError();
			if (ErrorStatus == ERROR_NO_MORE_ITEMS)	//Did we reach the end of the list of matching devices in the DeviceInfoTable?
			{	//Cound not find the device.  Must not have been attached.
				SetupDiDestroyDeviceInfoList(DeviceInfoTable);	//Clean up the old structure we no longer need.
				return "";		
			}
		}
		else	//Else some other kind of unknown error ocurred...
		{
			ErrorStatus = GetLastError();
			SetupDiDestroyDeviceInfoList(DeviceInfoTable);	//Clean up the old structure we no longer need.
			return "";	
		}

		//Now retrieve the hardware ID from the registry.  The hardware ID contains the VID and PID, which we will then 
		//check to see if it is the correct device or not.

		//Initialize an appropriate SP_DEVINFO_DATA structure.  We need this structure for SetupDiGetDeviceRegistryProperty().
		DevInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
		SetupDiEnumDeviceInfo(DeviceInfoTable, InterfaceIndex, &DevInfoData);

		//First query for the size of the hardware ID, so we can know how big a buffer to allocate for the data.
		SetupDiGetDeviceRegistryProperty(DeviceInfoTable, &DevInfoData, SPDRP_HARDWAREID, &dwRegType, NULL, 0, &dwRegSize);

		//Allocate a buffer for the hardware ID.
		PropertyValueBuffer = (BYTE *) malloc (dwRegSize);
		if(PropertyValueBuffer == NULL)	//if null, error, couldn't allocate enough memory
		{	//Can't really recover from this situation, just exit instead.
			SetupDiDestroyDeviceInfoList(DeviceInfoTable);	//Clean up the old structure we no longer need.
			return "";		
		}

		//Retrieve the hardware IDs for the current device we are looking at.  PropertyValueBuffer gets filled with a 
		//REG_MULTI_SZ (array of null terminated strings).  To find a device, we only care about the very first string in the
		//buffer, which will be the "device ID".  The device ID is a string which contains the VID and PID, in the example 
		//format "Vid_04d8&Pid_000a".
		SetupDiGetDeviceRegistryProperty(DeviceInfoTable, &DevInfoData, SPDRP_HARDWAREID, &dwRegType, PropertyValueBuffer, dwRegSize, NULL);

		//Now check if the first string in the hardware ID matches the device ID of my USB device.
		string DeviceIDFromRegistry;
		DeviceIDFromRegistry.assign((const char *)PropertyValueBuffer, dwRegSize);
		free(PropertyValueBuffer);		//No longer need the PropertyValueBuffer, free the memory to prevent potential memory leaks

		//Convert both strings to lower case.  This makes the code more robust/portable accross OS Versions
		DeviceIDFromRegistry = toLower(DeviceIDFromRegistry);
		DeviceIDToFind = toLower(DeviceIDToFind);
		//Now check if the hardware ID we are looking at contains the correct VID/PID
		if (DeviceIDFromRegistry.find(DeviceIDToFind) != string::npos)
		{
			//Device must have been found.  Now we should read the "FriendlyName".  The FriendlyName is the text
			//that appears in the device manager entry for the device.  The friendlyname for the virtual com port
			//is composed of the text from the .inf file used to install the driver, combined with the COMx number
			//assigned to the device.  The COMx number also appears in the registry "PortName" entry for the device.
			//First call the function to determine the size of the required buffer.
			SetupDiGetDeviceRegistryProperty(DeviceInfoTable, &DevInfoData, SPDRP_FRIENDLYNAME, &dwRegType, NULL, 0, &dwRegSize);
			//Allocate a buffer for the hardware ID.
			PropertyValueBuffer = (BYTE *) malloc (dwRegSize);
			if (PropertyValueBuffer == NULL)	//if null, error, couldn't allocate enough memory
			{	//Can't really recover from this situation, just exit instead.
				SetupDiDestroyDeviceInfoList(DeviceInfoTable);	//Clean up the old structure we no longer need.
				return "";		
			}

			//Retrieve the FriendlyName string containing the COMx number.
			SetupDiGetDeviceRegistryProperty(DeviceInfoTable, &DevInfoData, SPDRP_FRIENDLYNAME, &dwRegType, PropertyValueBuffer, dwRegSize, NULL);

			//Now copy the resulting string into a managed type for easier manipulation with
			//managed functions.
			FriendlyNameString.assign((const char *)PropertyValueBuffer, dwRegSize);
			free(PropertyValueBuffer);		//No longer need the PropertyValueBuffer, free the memory to prevent potential memory leaks

			//Check if it contains the COMx string like expected, and if so, extract it, so it can
			//be returned in the formatting used when opening a COMx port.
			if (FriendlyNameString.find("(COM") != -1 && FriendlyNameString.find(")") != -1)
			{
				IndexOfStartofCOMChars = FriendlyNameString.find("COM");
				IndexOfEndOfCOMChar = FriendlyNameString.find(")");
				if ((IndexOfEndOfCOMChar > IndexOfStartofCOMChars) && (IndexOfEndOfCOMChar <= FriendlyNameString.size()))
				{
					//Copy the "COMxx" string value from the FriendlyNameString to the COMString.
					//The COMString is formatted in the exact format needed when setting a serial port object's COM number.
					//COMString = FriendlyNameString->Substring(IndexOfStartofCOMChars, IndexOfEndOfCOMChar - IndexOfStartofCOMChars);
					COMString = FriendlyNameString.substr(IndexOfStartofCOMChars, IndexOfEndOfCOMChar - IndexOfStartofCOMChars);
				}
			}
				 
			//We now (most likely) found the COMx number and should return.
			SetupDiDestroyDeviceInfoList(DeviceInfoTable);	//Clean up the old structure we no longer need.
			return COMString;	//May contain "" if we couldn't find the device COMxx number.  Otherwise, returns "COMxxx" where xxx is the COM number (could be only one or two chars, depending upon COM number)
		}

		InterfaceIndex++;	
		//Keep looping until we either find a device with matching VID and PID, or until we run out of devices to check.
		//However, just in case some unexpected error occurs, keep track of the number of loops executed.
		//If the number of loops exceeds a very large number, exit anyway, to prevent inadvertent infinite looping.
		LoopCounter++;
		if (LoopCounter == 10000000) //Surely there aren't more than 10 million devices attached to any forseeable PC...
		{
			return "";
		}
	}//end of while(true)
}